import { Rectangle } from 'cesium';
import Cesium, { Cartesian3, Cartographic, Scene } from '../Ces/Cesium';
import DrawConfig from './DrawConfig';

// interface DrawObjectOptions {
//   type: 'line' | 'polyline' | 'rectangle' | 'polygon' | string;
//   isConstant?: boolean;
//   clampToGround?: boolean;
//   [key: string]: any;
// }

class DrawObject {
  private _outlinePrimitive: any// Cesium.Primitive | Cesium.GroundPolylinePrimitive | undefined;
  private _fillPrimitive:  any//Cesium.Primitive | Cesium.GroundPrimitive | undefined;
  private _isConstant: boolean;
  private _clampToGround: boolean;
  private _positions: Cartesian3[];
  private _type: string;
  private _rectangle: Rectangle | undefined;
  private _dirty: boolean = false;
  private _outlineAppearance:  any//Cesium.PolylineMaterialAppearance | undefined;
  private _appearance:  any//Cesium.Appearance | undefined;
  private _fillAppearance:  any//Cesium.Appearance | undefined;

  // Scratch variables for rectangle calculations
  private _scratchCartesian1: Cartesian3;
  private _scratchCartesian2: Cartesian3;
  private _scratchCartesian3: Cartesian3;
  private _scratchCartesian4: Cartesian3;
  private _scratchCartesian5: Cartesian3;
  private _scratchCartographic1: Cartographic;
  private _scratchCartographic2: Cartographic;

  constructor(options: any) {
    this._isConstant = options.isConstant?? false
    this._clampToGround = options.clampToGround?? false
    this._positions = [];
    this._type = options.type;

    if (options.type === 'rectangle') {
      this._rectangle = new Cesium.Rectangle();
      this._scratchCartesian1 = new Cesium.Cartesian3();
      this._scratchCartesian2 = new Cesium.Cartesian3();
      this._scratchCartesian3 = new Cesium.Cartesian3();
      this._scratchCartesian4 = new Cesium.Cartesian3();
      this._scratchCartesian5 = new Cesium.Cartesian3();
      this._scratchCartographic1 = new Cesium.Cartographic();
      this._scratchCartographic2 = new Cesium.Cartographic();
    }
  }

  get rectangle() {
    return this._rectangle
  }
  set rectangle(value) {
    this._rectangle = value
  }

  get positions(): Cartesian3[] {
    return this._positions;
  }
  set positions(value: Cartesian3[]) {
    this._positions = value
  }

  get isConstant(): boolean {
    return this._isConstant;
  }

  set isConstant(value: boolean) {
    this._isConstant = value;
  }

  get minNumPonint(): number {
    return (this._type === 'polygon') ? 3 : 2;
  }

  get maxNumPonint(): number {
    return (this._type === 'polyline' || this._type === 'polygon') ? 100 : 2;
  }

  get type(): string {
    return this._type;
  }

  get geometry(): Rectangle | Cartographic[] {
    return this.getGeometry();
  }

  get dirty(): boolean {
    return this._dirty;
  }

  set dirty(value: boolean) {
    this._dirty = value;
  }

  getTips(): string {
    if (this.positions.length === 0) {
      return '左键单击取点';
    }

    if (this._type === 'line' || this._type === 'rectangle') {
      return '再次单击完成(或右键取消)';
    }

    return '双击完成(或右键取消)';
  }

  getGeometry(): Rectangle | Cartographic[] {
    if (this._type === 'rectangle') {
      return this._rectangle!;
    }

    const points: Cartographic[] = [];
    this.positions.forEach((cartesian) => {
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      points.push(cartographic);
    });

    return points;
  }

  rebuild(scene: Scene): void {
    if (this.positions.length < 2) {
      return;
    }

    let outlinePrimitive
    let fillPrimitive

    if (this._type === 'line' || this._type === 'polyline') {
      outlinePrimitive = this.createPolyLine(this.positions, scene);
    } else if (this._type === 'rectangle') {
      fillPrimitive = this.createRectangle(this.positions, scene);
      if (DrawConfig.drawOutline) {
        outlinePrimitive = this.createPolyLine(this.positions, scene);
      }
    } else if (this._type === 'polygon') {
      fillPrimitive = this.createPolygon(this.positions);
      if (DrawConfig.drawOutline) {
        outlinePrimitive = this.createPolyLine(this.positions, scene);
      }
    }

    this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy();
    this._outlinePrimitive = outlinePrimitive;

    this._fillPrimitive = this._fillPrimitive && this._fillPrimitive.destroy();
    this._fillPrimitive = fillPrimitive;
  }

  private createPolyLine(positionsIn: Cartesian3[], scene: Scene){
    if (!Cesium.defined(this._outlineAppearance)) {
      this._outlineAppearance = new Cesium.PolylineMaterialAppearance({
        material: Cesium.Material.fromType(Cesium.Material.PolylineGlowType),
      });
      this._outlineAppearance.material.uniforms.color = Cesium.Color.fromCssColorString(DrawConfig.lineColor);
    }

    const width = DrawConfig.lineWidth;
    const appearance = this._outlineAppearance;
    const asynchronous = false;
    const arcType = this._type === 'rectangle' ? Cesium.ArcType.RHUMB : Cesium.ArcType.GEODESIC;

    let positions: Cartesian3[];

    if (this._type === 'rectangle') {
      const { west, south, east, north } = this._rectangle!;
      const ellipsoid = scene.mapProjection.ellipsoid;

      positions = [
        Cesium.Cartesian3.fromRadians(west, south, 0.0, ellipsoid, this._scratchCartesian1),
        Cesium.Cartesian3.fromRadians(east, south, 0.0, ellipsoid, this._scratchCartesian2),
        Cesium.Cartesian3.fromRadians(east, north, 0.0, ellipsoid, this._scratchCartesian3),
        Cesium.Cartesian3.fromRadians(west, north, 0.0, ellipsoid, this._scratchCartesian4),
        Cesium.Cartesian3.fromRadians(west, south, 0.0, ellipsoid, this._scratchCartesian5),
      ];
    } else if (this._type === 'polygon' && positionsIn.length > 2) {
      positions = positionsIn.concat([positionsIn[0]]);
    } else {
      positions = positionsIn;
    }

    if (this._clampToGround) {
      const geometryInstances = new Cesium.GeometryInstance({
        geometry: new Cesium.GroundPolylineGeometry({
          positions,
          width,
          arcType,
        }),
      });

      return new Cesium.GroundPolylinePrimitive({ geometryInstances, asynchronous, appearance });
    } else {
      const geometryInstances = new Cesium.GeometryInstance({
        geometry: new Cesium.PolylineGeometry({
          positions,
          width,
          arcType,
        }),
      });

      return new Cesium.Primitive({ geometryInstances, asynchronous, appearance });
    }
  }

  private createRectangle(positions: Cartesian3[], scene: Scene) {
    if (!Cesium.defined(this._appearance)) {
      this._appearance = getEllipsoidSurfaceAppearance();
    }

    const ellipsoid = scene.mapProjection.ellipsoid;
    const cartographic1 = Cesium.Cartographic.fromCartesian(positions[0], ellipsoid, this._scratchCartographic1);
    const cartographic2 = Cesium.Cartographic.fromCartesian(positions[1], ellipsoid, this._scratchCartographic2);

    const west = cartographic1.longitude < cartographic2.longitude ? cartographic1.longitude : cartographic2.longitude;
    const east = cartographic1.longitude > cartographic2.longitude ? cartographic1.longitude : cartographic2.longitude;
    const south = cartographic1.latitude < cartographic2.latitude ? cartographic1.latitude : cartographic2.latitude;
    const north = cartographic1.latitude > cartographic2.latitude ? cartographic1.latitude : cartographic2.latitude;

    const rectangle = Cesium.Rectangle.fromRadians(west, south, east, north, this._rectangle!);

    const geometryInstances = new Cesium.GeometryInstance({
      geometry: new Cesium.RectangleGeometry({ rectangle }),
    });

    this._rectangle = rectangle;

    const appearance = this._appearance;
    const asynchronous = false;

    return this._clampToGround
      ? new Cesium.GroundPrimitive({ geometryInstances, asynchronous, appearance })
      : new Cesium.Primitive({ geometryInstances, asynchronous, appearance });
  }

  private createPolygon(positions: Cartesian3[]) {
    if (!Cesium.defined(this._fillAppearance)) {
      this._fillAppearance = getEllipsoidSurfaceAppearance();
    }

    if (positions.length < 3) {
      return undefined;
    }

    const asynchronous = false;
    const appearance = this._fillAppearance;
    const geometryInstances = new Cesium.GeometryInstance({
      geometry: new Cesium.PolygonGeometry({
        polygonHierarchy: new Cesium.PolygonHierarchy(positions),
      }),
    });

    if (this._clampToGround) {
      return new Cesium.GroundPrimitive({ geometryInstances, asynchronous, appearance });
    } else {
      return new Cesium.Primitive({ geometryInstances, asynchronous, appearance });
    }
  }

  destroy(): void {
    this._fillPrimitive = this._fillPrimitive && this._fillPrimitive.destroy();
    this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy();
    Cesium.destroyObject(this);
  }

  isDestroyed(): boolean {
    return false;
  }

  update(frameState): void {
    if (this._dirty) {
      this._dirty = false;
      this.rebuild(frameState.camera._scene);
    }

    if (Cesium.defined(this._outlinePrimitive)) {
      this._outlinePrimitive.update(frameState);
    }
    if (Cesium.defined(this._fillPrimitive)) {
      this._fillPrimitive.update(frameState);
    }
  }
}

function getRenderState() {
  return Cesium.RenderState.fromCache({
    depthTest: { enabled: false },
    depthMask: false,
    blending: Cesium.BlendingState.ALPHA_BLEND,
    cull: {
      enabled: true,
      face: Cesium.CullFace.BACK,
    },
  });
}

function getEllipsoidSurfaceAppearance() {
  const renderState = getRenderState();
  const appearance = new Cesium.EllipsoidSurfaceAppearance({
    aboveGround: false,
    renderState,
    material: Cesium.Material.fromType('Color'),
  });

  appearance.material.uniforms.color = Cesium.Color.fromCssColorString(DrawConfig.fillColor);
  return appearance;
}

export default DrawObject;
